package current.limiting.aspect;

import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import current.limiting.annotions.Limiting;
import current.limiting.commom.exception.CurrentLimitException;
import current.limiting.service.CurrentLimitStrategy;
import current.limiting.utils.LimitUtil;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;

/**
 * 接口限流相关服务
 *
 * @author LiKun
 * @date 2021/10/25 10:48
 */
@Aspect
@Component
public final class CurrentLimitAspect {

    @Resource(name = "fixedTimeRedisCurrentLimitOptimizeService")
    private CurrentLimitStrategy currentLimitStrategy;

    @Around("@annotation(current.limiting.annotions.Limiting)")
    public Object currentLimitAspect(ProceedingJoinPoint joinPoint) throws Throwable {
        Class<?> targetClass = joinPoint.getTarget().getClass();
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();

        // 获取切面上的Limiting对象
        Limiting limiting = this.getLimiting(targetClass, signature);
        // 判断是否存在限流标识，如果不存在则为限流的方法设置对应的Key值，格式为 类名 + . + 方法名
        String id = StrUtil.isNotEmpty(limiting.value()) ? limiting.value() : LimitUtil.concatPoint(targetClass.getName(), signature.getName());

        if (currentLimitStrategy.isAllowAccess(id, limiting)) {
            return joinPoint.proceed();
        } else {
            throw new CurrentLimitException();
        }
    }

    /**
     * 根据切点所在类和切面方法获取对应的Limiting对象
     *
     * @param targetClass 切点所在类
     * @param signature   切面方法
     *
     * @return Limiting对象
     */
    private Limiting getLimiting(Class<?> targetClass, MethodSignature signature) {
        // 首先检测方法上是否存在 Limiting 注解
        Limiting limiting = signature.getMethod().getAnnotation(Limiting.class);
        // 如果被拦截的方法不存在 Limiting 注解，则检测改 class 类是否存在 Limiting 注解
        return ObjectUtil.isNotNull(limiting) ? limiting : targetClass.getAnnotation(Limiting.class);
    }

}
